import java.io.IOException;
import java.util.Iterator;


public class Eval {

	/**
	 * @param args
	 */
	public Alist alist;
	public Dlist dlist;
	public Eval()
	{
		this.alist = new Alist();
		this.dlist = new Dlist();		
	}
	
	public Sexpr eval(Sexpr sexpr, Alist alist, Dlist dlist)
	{
		Sexpr sexprNew;
		Sexpr ret = new Sexpr("NIL");
		Sexpr param;
		if(sexpr.isAtom == true)
		{
			if((getSexprType(sexpr) == TokenType.INT)||(getSexprType(sexpr) == TokenType.NIL)||(getSexprType(sexpr) == TokenType.T))
			{
				return sexpr;
			}
			sexpr = alist.getVal(sexpr.Value);
			if(sexpr == null)
			{
				System.err.println("Unbounded variable " + sexpr.Value);
				return null;
			}
			return sexpr;			
		}
		sexprNew = sexpr.car;
		if(sexprNew.isAtom == true)
		{
			switch(getSexprType(sexprNew))
			{
				case TokenType.QUOTE:
				{
					sexprNew = sexpr.cdr;					
					sexpr = sexprNew.car;
					if(getSexprType(sexprNew.cdr) != TokenType.NIL)
					{
						System.err.println("Invalid number of argument for Quote, should have 1 argument");
						return null;
					}
					return sexpr;
				}
				case TokenType.DEFUN:
				{
					sexprNew = sexpr.cdr;
					if(dlist.addFunction(sexprNew.car.Value, sexprNew.cdr) < 0)
					
					//if(dlist.addFunction(sexprNew.car.Value, sexprNew) < 0)
					{
						return null;
					}
					else
					{
						sexpr = sexprNew.car;
						System.out.println("The function name is: " + sexpr.Value);	
						break;
					}
				}
				case TokenType.COND:
				{
					ret = evcon(sexpr.cdr, alist, dlist);
					if(ret == null)
						return null;
					break;
				}
				default:
					param = evlis(sexpr.cdr, alist, dlist);
					if(param == null)
						return null;
					ret = apply(sexpr, param, alist, dlist);					
			}
			
		}
		
		return ret;	
	}
	public Sexpr evcon(Sexpr be, Alist alist, Dlist dlist)
	{
		Sexpr situtation = new Sexpr("NIL");
		Sexpr booleanvalue = new Sexpr("NIL");
		Sexpr ret = new Sexpr("NIL");
		Sexpr sexpr = new Sexpr("NIL");
		if(be.isNIL == true)
			return be;
		situtation = be;
		while(situtation.isNIL != true)
		{
			if(situtation.isAtom == true)
			{
				ret = null;
			}
			sexpr = situtation.car;
			if(sexpr.sexprLength(sexpr) != 2)
			{
				System.err.println("Invalid Conditions");
				ret = null;
				break;				
			}
			sexpr = sexpr.car;
			booleanvalue = eval(sexpr, alist, dlist);
			if(booleanvalue == null)
			{
				return null;
			}
			if(booleanvalue.isAtom != true)
			{
				System.err.println("The boolean expression should be an atom");
				return null;
			}
			switch(getSexprType(booleanvalue))
			{
				case TokenType.NIL:
					situtation = situtation.cdr;
					continue;
				case TokenType.T:
					sexpr = situtation.car;
					sexpr = sexpr.cdr;					
					sexpr = sexpr.car;
					ret = eval(sexpr, alist, dlist);
					if(ret == null)
						return null;
					break;
				default:
					System.err.println("The value of the boolean expression should be either T or NIL");
					return null;		
			}
			break;			
		}
		
		return ret;
	}
	public Sexpr evlis(Sexpr list, Alist alist, Dlist dlist)
	{
		Sexpr root, sexpr, result;
		sexpr = new Sexpr("NIL");
		root = sexpr;
		//int n = 0;
		while(list.isNIL != true)
		{
			if(list.isAtom == true)
			{
				System.err.println("The sexpr is not a list");
				break;
			}
			else
			{
				
				result = eval(list.car, alist, dlist);
				if(result == null)
					return null;
				sexpr.car = result;
				Sexpr sexprNew = new Sexpr("NIL");
				sexpr.cdr = sexprNew;
				sexpr.isAtom = false;
				sexpr.isID = false;
				sexpr.isNIL = false;
				sexpr.isNumber = false;
				//root = sexpr;
				sexpr = sexprNew;
				list = list.cdr;
				if(list.car == null)
				{
					list.isNIL = true;
					break;
				}
			}
			
		}
		return root;
	}
	public Alist addPairs(Sexpr funSexpr, Sexpr param, Alist alist)
	{
		Sexpr sexpr;
		while(param.isNIL != true)
		{
			if(param.isAtom == true)
			{
				return null;
			}
			if(getSexprType(funSexpr.car) != TokenType.ID)
			{
				System.err.println("In function " + funSexpr.Value+" : Lambda-variable is not a symbol");
			}
			String name = funSexpr.car.Value;
			funSexpr = funSexpr.cdr;
			alist.pushVal(name, param.car);
			param = param.cdr;
		}
		return alist;
	}
	public Sexpr apply(Sexpr fun, Sexpr param, Alist alist, Dlist dlist)
	{		
		Sexpr result;
		Sexpr function;
		result = null;
		BuiltinFunctions builtinfunctions = new BuiltinFunctions();
		if(fun.car.Value.equals("CAR"))
			result = builtinfunctions.car(param);
		else if(fun.car.Value.equals("CDR"))
			result = builtinfunctions.cdr(param);
		else if(fun.car.Value.equals("ATOM"))
			result = builtinfunctions.atom(param);
		else if(fun.car.Value.equals("INT"))
			result = builtinfunctions.intBuiltin(param);
		else if(fun.car.Value.equals("CONS"))
			result = builtinfunctions.cons(param);
		else if(fun.car.Value.equals("EQ"))
			result = builtinfunctions.eq(param);
		else if(fun.car.Value.equals("NULL"))
			result = builtinfunctions.Null(param);
		else if(fun.car.Value.equals("PLUS"))
			result = builtinfunctions.plus(param);
		else if(fun.car.Value.equals("MINUS"))
			result = builtinfunctions.minus(param);
		else if(fun.car.Value.equals("GREATER"))
			result = builtinfunctions.greater(param);
		else if(fun.car.Value.equals("LESS"))
			result = builtinfunctions.less(param);
		else if(fun.car.Value.equals("TIMES"))
			result = builtinfunctions.times(param);
		else if(fun.car.Value.equals("QUOTIENT"))
			result = builtinfunctions.quotient(param);
		else if(fun.car.Value.equals("REMAINDER"))
			result = builtinfunctions.remainder(param);
		else if(fun.car.isID == true)
		{
			function = dlist.getFunction(fun.car.Value);
/*			Iterator iterator = dlist.dlist.keySet().iterator();
			while (iterator.hasNext()) {
			       Object value = dlist.dlist.get(iterator.next());
			       System.out.println(value.toString());
			        }*/ 
			if(function == null)
			{
				System.err.println("Unbounded function!");
				return null;
			}
			if(this.addPairs(function.car, param, alist) == null)
			{
				System.err.println("Invalid argument for function");
				return null;
			}
			Sexpr evalSexpr = function.cdr.cloneSexpr(function.cdr.car);
			result = eval(evalSexpr, alist, dlist);
		}
		
		return result;
		
	}
	public int getSexprType(Sexpr sexpr)
	{
		if(sexpr.isAtom != true)
		{
			return -1;//The sexpr is not an atom;
		}
		else
		{
			
			if(sexpr.isNumber == true)
			{
				return TokenType.INT;
			}
/*			if(sexpr.isID == true)
			{*/
				if(sexpr.Value.equals("NIL"))
				{
					return TokenType.NIL;
				}
				else if(sexpr.Value.equals("T"))
				{
					return TokenType.T;
				}
				else if(sexpr.Value.equals("CAR"))
				{
					return TokenType.CAR;
				}
				else if(sexpr.Value.equals("CDR"))
				{
					return TokenType.CDR;
				}
				else if(sexpr.Value.equals("PLUS"))
				{
					return TokenType.PLUS;
				}
				else if(sexpr.Value.equals("MINUS"))
				{
					return TokenType.MINUS;
				}
				else if(sexpr.Value.equals("QUOTE"))
				{
					return TokenType.QUOTE;
				}
				else if(sexpr.Value.equals("CONS"))
				{
					return TokenType.CONS;
				}					
				else if(sexpr.Value.equals("EQ"))
				{
					return TokenType.EQ;
				}
				else if (sexpr.Value.equals("DEFUN"))
				{
					return TokenType.DEFUN;
				}
				else if(sexpr.Value.equals("ATOM"))
				{
					return TokenType.ATOM;
				}	
				else if(sexpr.Value.equals("COND"))
				{
					return TokenType.COND;
				}
				else if(sexpr.isID == true)
				{
					return TokenType.ID;//not a build in function.
				}				
			//}
			
		}
		return 0;//Means That is not a buildin function.
			
		
	}
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		String filename = new String("/home/lixinyu/workspace/Lisp/src/test.txt");
		Parser parser = new Parser(new Scanner());
		parser.scanner.readFileToString(filename);	
		System.out.println(parser.scanner.textString);
		SexprPrinter sexprPrinter = new SexprPrinter();		
		Sexpr sexprMain = parser.buildTree(null); 
		//sexprPrinter.sexprPrinter(sexprMain);
		Eval eval = new Eval();
		Sexpr evalSexpr = eval.eval(sexprMain, eval.alist, eval.dlist);
		sexprPrinter.sexprPrinter(evalSexpr);
	}

}
